package Client;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;

import DBTools.DBProcess;
import Structure.MeaningWordPair;
import Structure.Translation;
import Structure.Word;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;

/**
 * this class allow to to connect to a mongodb and add words to the db, clear it
 * or ask for translations of a word
 * 
 * @author Tony
 * 
 */
public class DBTransaction {

	Mongo m;
	DB db;
	DBCollection coll;

	private final HashMap<String, String> langCodes;

	/**
	 * initialize the mapping of the language codes to the complete language
	 */
	public DBTransaction() {
		this.langCodes = new HashMap<String, String>();

		// read langCodes from File and write them into the Map
		try {
			BufferedReader bf = new BufferedReader(new FileReader(
					"properties/deLangCodes.txt"));
			String tmp;
			while ((tmp = bf.readLine()) != null) {
				String[] splits = tmp.split(";");
				langCodes.put(splits[1], splits[0]);
			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();

		}
	}

	/**
	 * Connect to a mongodb instance on lokalhost. Create the db "translation"
	 * it does not exists
	 * 
	 * @return
	 */
	public boolean connect() {
		try {
			m = new Mongo("127.0.0.1");
			if (!m.getDatabaseNames().contains("translation")) {
				System.out.println("DB will be created");
			}
			db = m.getDB("translation");

			coll = db.getCollection("testCollection");
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		} catch (MongoException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}

		return true;
	}

	/**
	 * deletes db content, be careful!
	 */
	public void deleteDBContent() {
		db.dropDatabase();
		db = m.getDB("translation");
	}

	/**
	 * transform a word to a BasicDBObject for mongodb
	 * 
	 * @param word
	 * @return
	 */
	private BasicDBObject createDBObjectFromWord(Word word) {

		BasicDBObject doc = new BasicDBObject();

		doc.put("word", word.word);
		doc.put("language", word.language);
		doc.put("languageOfContent", word.languageOfContent);

		// explanation
		BasicDBObject docExplanation = new BasicDBObject();
		for (Entry<String, String> meaningToExplenation : word.mapMeaningToExplanation
				.entrySet()) {
			docExplanation.put(meaningToExplenation.getKey(),
					meaningToExplenation.getValue());
		}

		doc.put("explanations", docExplanation);

		// pro language
		for (int t = 0; t < word.translations.length; t++) {

			BasicDBObject translation = new BasicDBObject();
			// pro meaning + word

			HashMap<String, LinkedList<String>> meanings = new HashMap<String, LinkedList<String>>();

			// change structure
			for (int j = 0; j < word.translations[t].meaningWordMap.size(); j++) {
				// if meaning not exists, then create it
				if (meanings.get(""
						+ word.translations[t].meaningWordMap.get(j).meaning) == null)
					meanings.put(
							""
									+ word.translations[t].meaningWordMap
											.get(j).meaning,
							new LinkedList<String>());

				// add word to meaning
				meanings.get(
						"" + word.translations[t].meaningWordMap.get(j).meaning)
						.add(word.translations[t].meaningWordMap.get(j).word);
			}

			// put word in right order to the database
			Iterator<String> keys = new TreeSet<String>(meanings.keySet())
					.iterator();

			while (keys.hasNext()) {
				String meaning = keys.next();
				LinkedList<String> wordsForMeaning = meanings.get(meaning);

				translation.put(meaning.toString(), wordsForMeaning);

			}

			doc.put(word.translations[t].language, translation);
		}

		return doc;
	}

	/**
	 * add a Word to the database, duplicate entries are possible!
	 * 
	 * @param word
	 */
	public void addWord(Word word) {

		BasicDBObject doc = this.createDBObjectFromWord(word);

		coll.insert(doc);
	}

	/**
	 * add a list of words like a single word {@link this.addWord(Word word)}
	 * 
	 * @param words
	 */
	public void addWord(List<Word> words) {

		LinkedList<DBObject> docs = new LinkedList<DBObject>();

		Iterator<Word> it = words.iterator();
		while (it.hasNext()) {
			BasicDBObject doc = this.createDBObjectFromWord(it.next());
			docs.add(doc);
		}

		coll.insert(docs);
	}

	/**
	 * returns all words that contains translation of the given words
	 * 
	 * @param word
	 * @return
	 */
	public Word[] findTranslations(String word) {
		BasicDBObject query = new BasicDBObject();
		Word[] w;

		query.put("word", word);

		DBCursor cur = coll.find(query);
		w = new Word[cur.size()];

		int i = 0;
		while (cur.hasNext()) {
			w[i] = createWordFromDBObject(cur.next());

			i++;
			// System.out.println(cur.next());
		}
		return w;
	}

	/**
	 * create a word from an DBObject
	 * @param o
	 * @return
	 */
	public Word createWordFromDBObject(DBObject o) {

		// change from short to long language term
		String longLanguage = this.getLongLanguageIfExists((String) o
				.get("language"));

		Word word = new Word((String) o.get("word"), longLanguage,
				(String) o.get("languageOfContent"));

		// get language of possible translations
		Set<String> s = o.keySet();
		s.remove("word");
		s.remove("language");
		s.remove("languageOfContent");
		s.remove("_id");

		if (s.contains("explanations")) {

			DBObject expl = (DBObject) o.get("explanations");
			Set<String> setMeanings = expl.keySet();

			for (String meaning : setMeanings) {
				String explenation = (String) expl.get(meaning);

				word.mapMeaningToExplanation.put(meaning, explenation);
			}

			s.remove("explanations");
		}

		// go through translatinos
		Iterator<String> it = s.iterator();
		word.translations = new Translation[s.size()];
		int j = 0;
		while (it.hasNext()) {

			String lang = it.next();

			DBObject to = (DBObject) o.get(lang);
			Set<String> mean = to.keySet();
			mean.remove("_id");

			Iterator<String> itT = mean.iterator();
			// LinkedList<Integer> meaning = new LinkedList<Integer>();
			LinkedList<MeaningWordPair> translation = new LinkedList<MeaningWordPair>();

			// go throuh words in each Translation
			while (itT.hasNext()) {
				String next = itT.next();

				BasicDBList wordsOfMeaning = (BasicDBList) to.get(next);

				for (int k = 0; k < wordsOfMeaning.size(); k++) {
					MeaningWordPair t = new MeaningWordPair(
							Integer.parseInt(next),
							(String) wordsOfMeaning.get(k));
					translation.add(t);
				}
			}
			// change from short to long language term
			lang = this.getLongLanguageIfExists(lang);

			// add the translation to the word
			Translation t = new Translation(lang, translation);
			word.translations[j] = t;
			j++;
		}
		return word;
	}

	/**
	 * find all translations of the given words with the given parameters
	 * @param word
	 * @param originLng can be null
	 * @param destLng can be null
	 * @param languageOfContent the language of the wiktonary where you want to search for the word - can be null
	 * @return
	 */
	public Word[] findTranslations(String word, String originLng,
			String destLng, String languageOfContent) {
		BasicDBObject query = new BasicDBObject();
		Word[] w;

		// set origin language
		query.put("word", word);
		if (originLng != null)
			query.put("language", originLng);

		// set language of content
		if (languageOfContent != null)
			query.put("languageOfContent", languageOfContent);

		// set destination language
		if (destLng != null) {
			BasicDBObject o = new BasicDBObject("$exists", true);
			query.append(destLng, o);
		}

		DBCursor cur = coll.find(query);
		w = new Word[cur.size()];

		int i = 0;
		while (cur.hasNext()) {
			w[i] = createWordFromDBObject(cur.next());

			i++;
			// System.out.println(cur.next());
		}
		return w;
	}

	/**
	 * returns the full language name if one exists, if not, than this function
	 * returns the code itself
	 * 
	 * @param languageCode
	 * @return
	 */
	public String getLongLanguageIfExists(String languageCode) {
		String longLanguage = languageCode;
		longLanguage = this.langCodes.get(longLanguage);
		if (longLanguage == null) {
			longLanguage = languageCode;
		}
		return longLanguage;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		DBProcess p = new DBProcess();
		p.start();

		DBTransaction t = new DBTransaction();
		t.connect();
		System.out.println("ask");

		// t.addWord(new Word("ente", "de",t);
		// Word[] w = t.findTranslations("ente", null, "en", null);
		// for (int i = 0; i < w.length; i++) {
		// System.out.println(w[i]);
		// }

		System.out.println("finish, close");
		p.stop();
	}

}
